const pushProduced = (element, callback = (a) => { return a }) => {
    //this must bind an Array
    //这个函数必须绑定在数组对象上!
    element = callback(element);
    this.push(element);
}

const getUTF8Bytes = (str) => {
    var bytes = [];
    var len = str.length;
    for (var i = 0; i < len; ++i) {
        var code = str.charCodeAt(i);
        if (code >= 0x10000 && code <= 0x10ffff) {
            bytes.push((code >> 18) | 0xf0);
            bytes.push(((code >> 12) & 0x3f) | 0x80);
            bytes.push(((code >> 6) & 0x3f) | 0x80);
            bytes.push((code & 0x3f) | 0x80);
        } else if (code >= 0x800 && code <= 0xffff) {
            bytes.push((code >> 12) | 0xe0);
            bytes.push(((code >> 6) & 0x3f) | 0x80);
            bytes.push((code & 0x3f) | 0x80);
        } else if (code >= 0x80 && code <= 0x7ff) {
            bytes.push((code >> 6) | 0xc0);
            bytes.push((code & 0x3f) | 0x80);
        } else {
            bytes.push(code)
        }
    }
    return new Uint8Array(bytes);
}

const getUTF8BytesP = (str, cb = (a) => { return a }) => {
    let bit1, bit2, bit3, bit4;
    var bytes = [];
    var len = str.length;
    let cnt = 0;
    for (var i = 0; i < len; ++i) {
        var code = str.charCodeAt(i);
        if (code >= 0x10000 && code <= 0x10ffff) {
            bit1 = (code >> 18) | 0xf0;
            bit2 = ((code >> 12) & 0x3f) | 0x80;
            bit3 = ((code >> 6) & 0x3f) | 0x80;
            bit4 = (code & 0x3f) | 0x80;
            bytes.push(...cb([bit1, bit2, bit3, bit4], cnt));
            cnt += 4;
        } else if (code >= 0x800 && code <= 0xffff) {
            bit1 = (code >> 12) | 0xe0;
            bit2 = ((code >> 6) & 0x3f) | 0x80;
            bit3 = (code & 0x3f) | 0x80;
            bytes.push(...cb([bit1, bit2, bit3], cnt));
            cnt += 3;
        } else if (code >= 0x80 && code <= 0x7ff) {
            bit1 = (code >> 6) | 0xc0;
            bit2 = (code & 0x3f) | 0x80;
            bytes.push(...cb([bit1, bit2], cnt));
            cnt += 2
        } else {
            bit1 = code;
            bytes.push(...cb([bit1], cnt));
            cnt++;
        }
    }
    return new Uint8Array(bytes);
}

const utf8Restore = (bytesArr, callback = (a) => { return a; }) => {
    let arr = [];
    bytesArr = bytesArr.forEach((item, index) => {
        item = callback(item, index);
        item = `%${item.toString(16)}`;
        arr.push(item);
    });
    return arr.join("");
}

const utf8 = { getUTF8Bytes, utf8Restore, getUTF8BytesP };

const reverseKey = (pwd = "", proto_str) => {
    pwd = [...utf8.getUTF8Bytes(pwd)];
    pwd = pwd.map(item => { return item % proto_str.length });
    let compare = {},
        cnt = 0,
        newKeyArr = [];
    for (let i = 0; i < pwd.length;) {
        if (cnt > proto_str.length) {
            //防止密码过长导致计算卡住，长度为模长即可
            break;
        }
        if (compare[pwd[i]] == true) {
            pwd.splice(i, 1); //剪切重复的内容
        } else {
            if (compare[pwd[i]] == void 0) {
                compare[pwd[i]] = true;
                newKeyArr.push(proto_str.charAt(pwd[i]));
                cnt++;
                i++;
                continue;
            }
            cnt++;
            i++;
        }
    }

    //顺序补全余下空位
    for (let i = 0; i < proto_str.length; i++) {
        let item = proto_str.charAt(i);
        //compare里未标记的元素直接push进入新key
        if (!compare[i]) newKeyArr.push(item);
    }
    return newKeyArr.join("");
}

const genBase64Key = (pwd) => {
    let proto_str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    return reverseKey(pwd, proto_str);
}

class base64 {
    constructor(pwd = void 0) {
        this._proto_key_ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
        this._keyStr = pwd != void 0 && pwd != "" ? genBase64Key(pwd) : this._proto_key_;
    }
    encode(uint8Arr) {
        var t = "";
        var n, r, i, s, o, u, a;
        for (let j = 0; j < uint8Arr.length;) {
            n = uint8Arr[j++];
            r = uint8Arr[j++];
            i = uint8Arr[j++];
            s = n >> 2;
            o = (n & 3) << 4 | r >> 4;
            u = (r & 15) << 2 | i >> 6;
            a = i & 63;
            if (isNaN(r)) {
                u = a = 64
            } else if (isNaN(i)) {
                a = 64
            }
            t = t + this._keyStr.charAt(s) + this._keyStr.charAt(o) + this._keyStr.charAt(u) + this._keyStr.charAt(a);
        }
        return t
    }
    decode(e) {
        var t = "";
        var n, r, i;
        var s, o, u, a;
        var f = 0;
        let bytes = [];
        e = e.replace(/[^A-Za-z0-9+/=]/g, "");
        while (f < e.length) {
            s = this._keyStr.indexOf(e.charAt(f++));
            o = this._keyStr.indexOf(e.charAt(f++));
            u = this._keyStr.indexOf(e.charAt(f++));
            a = this._keyStr.indexOf(e.charAt(f++));
            n = s << 2 | o >> 4;
            r = (o & 15) << 4 | u >> 2;
            i = (u & 3) << 6 | a;
            t = t + String.fromCharCode(n);
            bytes.push(n);
            if (u != 64) {
                t = t + String.fromCharCode(r);
                bytes.push(r);
            }
            if (a != 64) {
                t = t + String.fromCharCode(i)
                bytes.push(i);
            }
        }
        return new Uint8Array(bytes);
    }
}



class A {
    constructor(pwd, hashCodeBytesCb = () => { return null; }) {

        //去除md5(在外部自行添加)
        this.hashCode = pwd;
        this.hashCodeBytes = utf8.getUTF8Bytes(this.hashCode);
        if (!hashCodeBytesCb) {
            //默认情况
            this.hashCodeRules = this.hashCodeBytes.map((item, index) => {
                let sum = 0;
                while (item > 0) {
                    sum += item % 10;
                    item = parseInt(item / 10);
                };
                return sum;
            });
        } else {
            this.hashCodeRules = this.hashCodeBytes.map(hashCodeBytesCb);
        }
    }
    encrypt(key, data) {
        data = JSON.stringify(data);
        data = utf8.getUTF8BytesP(data,
            (item, index) => {
                for (let i = 0; i < item.length; i++, index++) {
                    if (index % 2 == 0) item[i] += this.hashCodeRules[index % this.hashCodeRules.length];
                    else item[i] -= this.hashCodeRules[index % this.hashCodeRules.length];
                }
                return item;
            }
        );
        this.b64 = new base64(key);
        return this.b64.encode(data);
    }
    decrypt(key, data) {
        if (!this.b64) this.b64 = new base64(key);
        data = this.b64.decode(data);
        let str = "";
        data = utf8.utf8Restore(data,
            (item, index) => {
                if (index % 2 == 0) item -= this.hashCodeRules[index % this.hashCodeRules.length];
                else item += this.hashCodeRules[index % this.hashCodeRules.length];
                str += item + " ";
                return item;
            }
        );
        let res;
        try {
            res = decodeURIComponent(data);
        } catch (err) {
            return null;
        }
		
		res = res.split("");
		res.shift();
		res.pop();
		res = res.join("");
        return res;
    }


}

module.exports = A;